package mnet

/*
 * session: using message id or remote procedure name with big codec.
 */

import (
	"encoding/binary"
	"math"
	"sync/atomic"

	"github.com/vmihailenco/msgpack"
)

// message process function:
// msgId can be string(remote function name) or uint16 message id type, data is variadic parameter list.
type MessageProcessFunc func(s *Session, msgId interface{}, para ...interface{})

// establish function:
// isConnect is connected(true) or terminate(false) state.
type EstablishFunc func(s *Session, isConnect bool)

// must initialize DealerFunc and EstablishFunc member outside.
type Session struct {
	conn          IConnection        // net connection.
	state         int32              // state flag,1 connected, 0 else.
	DealerFunc    MessageProcessFunc // message dealer function, can be visited outside.
	EstablishFunc EstablishFunc      // establish function, can visit outside.
	Attach        interface{}        // session attach
}

// SetDealer sets message dealer function.
func (rs *Session) SetDealer(dealerFunc MessageProcessFunc) {
	rs.DealerFunc = dealerFunc
}

// SetEstablish sets establish function
func (rs *Session) SetEstablish(esFunc EstablishFunc) {
	rs.EstablishFunc = esFunc
}

// SetAttach set attach info.
func (rs *Session) SetAttach(a interface{}) {
	rs.Attach = a
}

// GetAttach gets attach.
func (rs *Session) GetAttach() interface{} {
	return rs.Attach
}

// OnRecv receives a network message callback.
func (rs *Session) OnRecv(msg []byte) {
	if len(msg) < int(gMsgDataLen+gMsgIdLen) {
		log().Error("[net] Message size error")
		return
	}

	msgId := binary.BigEndian.Uint16(msg[gMsgDataLen:])
	if math.MaxUint16 == msgId {
		// rpc message call mode.
		var rpcData RPCMessageInfo
		if err := msgpack.Unmarshal(msg[gMsgDataLen+gMsgIdLen:], &rpcData); err != nil {
			log().Error("[net] msgpack.Decode content: ", msg, " error:", err)
			return
		}
		if rs.DealerFunc != nil {
			rs.DealerFunc(rs, rpcData.FuncName, rpcData.Para...)
		} else {
			log().Error("[net] Dealer function is nil")
		}
	} else {
		// message id call mode
		if rs.DealerFunc != nil {
			rs.DealerFunc(rs, msgId, msg[gMsgDataLen+gMsgIdLen:])
		} else {
			log().Error("[net] Dealer function is nil")
		}
	}
}

// Call posts a rpc call message(function name and parameter list)
func (rs *Session) Call(funcName string, para ...interface{}) {
	rpcMessageInfo := RPCMessageInfo{
		FuncName: funcName,
		Para:     para,
	}
	msgBytes, _ := msgpack.Marshal(&rpcMessageInfo)
	rs.SendMsg(math.MaxUint16, msgBytes)
}

// Send sends message id and message.
func (rs *Session) SendMsg(msgId uint16, data []byte) {
	rs.Send(gMyMsgCodec.BuildProto(msgId, data))
}

// Send binary message.
func (rs *Session) Send(data []byte) {
	if rs.IsConnected() {
		rs.conn.SendMsg(data)
	}
}

// OnEstablish builds a establish connection callback
func (rs *Session) OnEstablish() {
	atomic.StoreInt32(&rs.state, gOpenFlag)
	if rs.EstablishFunc != nil {
		rs.EstablishFunc(rs, true)
	}
}

// OnTerminate terminates a connection callback
func (rs *Session) OnTerminate() {
	atomic.StoreInt32(&rs.state, gClosedFlag)
	if rs.EstablishFunc != nil {
		rs.EstablishFunc(rs, false)
	}
}

// SetConnection sets a IConnection
func (rs *Session) SetConnection(conn IConnection) {
	rs.conn = conn
}

// IsConnected Whether it is connected now
func (rs *Session) IsConnected() bool {
	return atomic.LoadInt32(&rs.state) == gOpenFlag
}

// GetConn gets the net connection.
func (rs *Session) GetConn() IConnection {
	return rs.conn
}

// close interface.
func (rs *Session) Close() {
	if rs.IsConnected() {
		rs.conn.Close()
	}
}
